//Copyright (c) 2006-2007 Microsoft Corporation.  All rights reserved
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Reflection;
using System.Web.Services.Protocols;
using System.Xml;

using InvocationFramework.Common;
using InvocationFramework.Common.Exceptions;
using InvocationFramework.Common.Instrumentation;
using InvocationFramework.Common.Interfaces;
using InvocationFramework.Common.Library;
using InvocationFramework.Common.Resources;

namespace InvocationFramework.Adapters.WebServices
{
    /// <summary>
    /// </summary>
    /// <author>Rodolfo Finochietti 04/12/2006 20.31</author>
    public class WebServicesAdapter : IServiceAdapter
    {
        #region Constants

        public const string CredentialsContextKey = "WebServiceAdapterCredentials";
        
        #endregion

        #region Private Vars

        private Dictionary<string, object> _serviceProxies = new Dictionary<string, object>( );

        private Dictionary<string, MethodInfo> _operationProxies = new Dictionary<string, MethodInfo>( );

        private bool _useCache = true;
        
        private object syncRoot = new object( );

        #endregion

        #region Constructors

        public WebServicesAdapter( ) { }

        #endregion

        #region IServiceAdapter Members

        private string _id;

        public string Id
        {
            get
            {
                return _id;
            }
        }

        private bool _initialized;

        public bool Initialized
        {
            get
            {
                return _initialized;
            }
        }

        public void Init( string id, XmlNode configuration )
        {
            if ( !_initialized )
            {
                try
                {
                    if ( String.IsNullOrEmpty( id ) )
                        throw new ArgumentNullException( "id", String.Format( CultureInfo.InvariantCulture, Texts.WebServicesAdapterArgumentNull, "id" ) );

                    if ( configuration == null )
                        throw new ArgumentNullException( "configuration", String.Format( CultureInfo.InvariantCulture, Texts.WebServicesAdapterArgumentNull, "configuration" ) );

                    _id = id;

                    _useCache = ( configuration.Attributes[ "useCache" ] == null ? true : Convert.ToBoolean( configuration.Attributes[ "useCache" ].Value, CultureInfo.InvariantCulture ) );
                    bool clearCache = ( configuration.Attributes[ "clearCache" ] == null ? false : Convert.ToBoolean( configuration.Attributes[ "clearCache" ].Value, CultureInfo.InvariantCulture ) );

                    if ( clearCache )
                        ProxyAssemblyCache.ClearCache( );
                    
                    CreateServiceProxies( configuration.SelectNodes( "service" ) );

                    _initialized = true;

                    TraceHelper.Verbose( String.Format( CultureInfo.InvariantCulture, Texts.WebServicesAdapterInitTrace, _useCache, clearCache ) );
                }
                catch ( Exception ex )
                {
                    TraceHelper.Error( ex, Texts.WebServicesAdapterInitError );
                    throw;
                }
            }
        }

        public bool IsServiceAvailable( string serviceName )
        {
            if ( !_initialized )
                throw new InvocationFrameworkException( Texts.WebServicesAdapterNotInitialized );

            return _serviceProxies.ContainsKey( serviceName );
        }

        public bool IsServiceOperationAvailable( string serviceName, string operationName )
        {
            if ( !_initialized )
                throw new InvocationFrameworkException( Texts.WebServicesAdapterNotInitialized );

            return _operationProxies.ContainsKey( GetKey( serviceName, operationName ) );
        }

        public string[] GetMostRequestedServices( )
        {
            if ( !_initialized )
                throw new InvocationFrameworkException( Texts.WebServicesAdapterNotInitialized );

            return null;
        }

        public object Execute( string serviceName, string operationName, ContextDictionary context, params object[] parameters )
        {
            if ( !_initialized )
                throw new InvocationFrameworkException( Texts.WebServicesAdapterNotInitialized );

            if ( !IsServiceAvailable( serviceName ) )
                throw new InvocationFrameworkException( String.Format( CultureInfo.InvariantCulture, Texts.WebServicesAdapterServiceNotFound, serviceName ) );

            string key = GetKey( serviceName, operationName );
            
            if ( !_operationProxies.ContainsKey( key ) )
                throw new InvocationFrameworkException( String.Format( CultureInfo.InvariantCulture, Texts.WebServicesAdapterOperationNotFound, serviceName, operationName ) );

            object proxy = _serviceProxies[ serviceName ];

            if ( context != null && context.Count > 0 && context.Contains( CredentialsContextKey ) )
            {
                SoapHttpClientProtocol soapClient = ( SoapHttpClientProtocol )proxy;
                soapClient.Credentials = context[ CredentialsContextKey ] as ICredentials;
            }
            
            MethodInfo method = _operationProxies[ key ];
            if ( method == null )
            {
                method = ReflectionHelper.GetMethod( proxy, operationName );

                lock ( syncRoot )
                {
                    _operationProxies[ key ] = method;
                }
            }

            try
            {
                return ReflectionHelper.InvokeMember( method, proxy, parameters );
            }
            catch ( Exception ex )
            {
                if ( ex.InnerException == null )
                    throw;
                else
                    throw ex.InnerException;
            }
        }

        #endregion

        #region Private Methods

        [System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes" )]
        private void CreateServiceProxies( XmlNodeList services )
        {
            if ( services != null )
            {
                foreach ( XmlNode node in services )
                {
                    if ( node.NodeType != XmlNodeType.Element )
                        continue;

                    string name = ( node.Attributes[ "name" ] == null ? String.Empty : node.Attributes[ "name" ].Value );
                    string wsdlLocation = ( node.Attributes[ "wsdlLocation" ] == null ? String.Empty : node.Attributes[ "wsdlLocation" ].Value );
                    DiscoveryProtocol protocol = ( node.Attributes[ "discoveryProtocol" ] == null ? DiscoveryProtocol.Soap : ( DiscoveryProtocol )Enum.Parse( typeof( DiscoveryProtocol ), node.Attributes[ "discoveryProtocol" ].Value ) );
                    string contractType = ( node.Attributes[ "contractType" ] == null ? String.Empty : node.Attributes[ "contractType" ].Value );

                    try
                    {
                        if ( String.IsNullOrEmpty( wsdlLocation ) )
                        {
                            TraceHelper.Error( Texts.WebServicesAdapterCreateServiceArgumentNull, "wsdlLocation" );
                        }
                        else
                        {
                            Type contract = ReflectionHelper.GetType( contractType );

                            if ( String.IsNullOrEmpty( name ) )
                                name = contract.Name;

                            Assembly proxyAssembly = ( _useCache ? ProxyAssemblyCache.GetProxy( wsdlLocation ) : null );
                            if ( proxyAssembly == null )
                            {
                                ProxyBuilder factory = new ProxyBuilder( wsdlLocation, protocol, contract );
                                proxyAssembly = factory.Build( );

                                ProxyAssemblyCache.AddProxy( wsdlLocation, proxyAssembly.Location );
                                
                                factory = null; //Clean up resources
                            }

                            object serviceProxy = ReflectionHelper.CreateObject( proxyAssembly.GetTypes( )[ 0 ] );
                            
                            lock ( syncRoot )
                            {
                                _serviceProxies.Add( name, serviceProxy );
                                CreateOperationProxies( name, contract );
                            }

                            contract = null; //Clean up resources

                            TraceHelper.Verbose( Texts.WebServicesAdapterCreateServiceProxyCreated, name, wsdlLocation, contractType );
                        }
                    }
                    catch ( Exception ex )
                    {
                        TraceHelper.Error( ex, Texts.WebServicesAdapterCreateServiceError, name, wsdlLocation, contractType );
                    }
                }
            }
        }

        private void CreateOperationProxies( string serviceName, Type contract )
        {
            MethodInfo[] methods = contract.GetMethods( );
            foreach ( MethodInfo method in methods )
            {
                string key = GetKey( serviceName, method.Name );
                if ( !_operationProxies.ContainsKey( key ) )
                    _operationProxies.Add( key, null );
            }
        }

        private static string GetKey( string serviceName, string operationName )
        {
            return String.Format( CultureInfo.InvariantCulture, "{0}\\{1}", serviceName, operationName );
        }

        #endregion
    }
}